/*
 * Copyright (c) 2023, mdf Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-10-20     MacRsh       First version
 */

#include "mdf_api.h"

static struct mdf_list mdf_dev_list = {&mdf_dev_list, &mdf_dev_list};

struct mdf_desc
{
    struct mdf_dev *dev;

    uint32_t oflags;
    int offset;
} mdf_desc_map[MDF_DESC_MAX] = {0};

#define desc_of(desc)                   (mdf_desc_map[(desc)])
#define desc_is_valid(desc)             (((desc) >= 0 && (desc) < MDF_DESC_MAX) && ((desc_of(desc).dev) != NULL))

static struct mdf_dev *dev_find_from_list(struct mdf_list *list, const char *name)
{
    /* Disable interrupt */
    mdf_interrupt_disable();

    /* Find the device */
    struct mdf_list *l = NULL;
    for (l = list->next; l != list; l = l->next)
    {
        struct mdf_dev *dev = (struct mdf_dev *)mdf_container_of(l, struct mdf_dev, list);
        if (strncmp(name, dev->name, MDF_NAME_MAX) == 0)
        {
            /* Enable interrupt */
            mdf_interrupt_enable();
            return dev;
        }
    }

    /* Enable interrupt */
    mdf_interrupt_enable();
    return NULL;
}

static void dev_register_list(struct mdf_dev *dev, const char *name, struct mdf_list *list)
{
    /* Disable interrupt */
    mdf_interrupt_disable();

    /* Insert the list */
    strncpy(dev->name, name, MDF_NAME_MAX);
    mdf_list_insert_before(list, &dev->list);

    /* Set magic */
    dev->magic = MDF_MAGIC;

    /* Enable interrupt */
    mdf_interrupt_enable();
}

static struct mdf_dev *dev_find_or_register(const char *name, struct mdf_dev *dev, int find_or_register)
{
    struct mdf_dev *dev_prev = NULL;
    struct mdf_dev *dev_next = NULL;
    size_t i = 0, j = 0;

    for (i = 0; name[i] != '\0'; i += j)
    {
        char name_cpy[MDF_NAME_MAX + 1] = {0};
        for (j = 0; j <= MDF_NAME_MAX; j++)
        {
            if ((name[j + i] == '\0') || (name[j + i] == '/'))
            {
                name_cpy[j] = '\0';
                if (i == 0)
                {
                    /* First register */
                    dev_prev = dev_find_from_list(&mdf_dev_list, name_cpy);
                    if (find_or_register == 0)
                    {
                        if (dev_prev == NULL)
                        {
                            return NULL;
                        }
                        if (name[j + i] == '\0')
                        {
                            return dev_prev;
                        }
                    } else
                    {
                        if (dev_prev == NULL)
                        {
                            if (name[j + i] != '/')
                            {
                                dev_register_list(dev, name_cpy, &mdf_dev_list);
                                return dev;
                            }
                            return NULL;
                        }
                    }
                } else
                {
                    /* Second register */
                    dev_next = dev_find_from_list(&dev_prev->slist, name_cpy);
                    if (find_or_register == 0)
                    {
                        if (dev_next == NULL)
                        {
                            return NULL;
                        }
                        if (name[j + i] == '\0')
                        {
                            return dev_next;
                        }
                    } else
                    {
                        if (dev_next == NULL)
                        {
                            if (name[j + i] != '/')
                            {
                                dev_register_list(dev, name_cpy, &dev_prev->slist);
                                dev->link = dev_prev;
                                return dev;
                            }
                            return NULL;
                        }
                        dev_prev = dev_next;
                    }
                }
                if (name[j + i] == '/')
                {
                    j++;
                }
                break;
            }
            name_cpy[j] = name[j + i];
        }
    }
    return NULL;
}

static int dev_register(struct mdf_dev *dev, const char *name)
{
    if (dev_find_or_register(name, dev, 1) != NULL)
    {
        return MDF_EOK;
    }
    return MDF_EINVAL;
}

MDF_INLINE int dev_open(int desc, uint32_t oflags)
{
    struct mdf_dev *dev = desc_of(desc).dev;

#if (MDF_CFG_RDWR_CTRL == MDF_CFG_ENABLE)
    if (oflags != (oflags & dev->sflags))
    {
        return MDF_ENOTSUP;
    }
#endif /* MDF_CFG_RDWR_CTRL */

    if (dev->ref_count == 0)
    {
        if (dev->ops->open != NULL)
        {
            return dev->ops->open(dev);
        }
    }
    dev->ref_count++;
    return MDF_EOK;
}

MDF_INLINE int dev_close(int desc)
{
    struct mdf_dev *dev = desc_of(desc).dev;

    dev->ref_count--;
    if (dev->ref_count == 0)
    {
        if (dev->ops->close != NULL)
        {
            return dev->ops->close(dev);
        }
    }
    return MDF_EOK;
}

MDF_INLINE ssize_t dev_read(int desc, int off, void *buf, size_t size, uint32_t sync_or_async)
{
    struct mdf_dev *dev = desc_of(desc).dev;

#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
    if (dev->lock & MDF_LOCK_RD)
    {
        return MDF_EBUSY;
    }
    dev->lock |= MDF_LOCK_RD;
    dev->lock |= (desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */

    /* Read buf from the device */
    ssize_t ret = dev->ops->read(dev, off, buf, size, sync_or_async);

#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
    dev->lock &= ~MDF_LOCK_RD;
    dev->lock &= ~(desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */
    return ret;
}

MDF_INLINE ssize_t dev_write(int desc, int offset, const void *buf, size_t size, uint32_t sync_or_async)
{
    struct mdf_dev *dev = desc_of(desc).dev;

#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
    if (dev->lock & MDF_LOCK_WR)
    {
        return MDF_EBUSY;
    }
    dev->lock |= MDF_LOCK_WR;
    dev->lock |= (desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */

    /* Write buf to the device */
    ssize_t ret = dev->ops->write(dev, offset, buf, size, sync_or_async);

#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
    dev->lock &= ~MDF_LOCK_WR;
    dev->lock &= ~(desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */
    return ret;
}

static int dev_ioctl(int desc, int off, int cmd, void *args)
{
    struct mdf_dev *dev = desc_of(desc).dev;

    if (dev->ops->ioctl == NULL)
    {
        return MDF_ENOTSUP;
    }

    switch (cmd)
    {
        case MDF_IOCTL_SET_RD_CB:
        {
            dev->rd_cb.desc = desc;
            dev->rd_cb.cb = (int (*)(int desc, void *args))args;
            return MDF_EOK;
        }
        case MDF_IOCTL_SET_WR_CB:
        {
            dev->wr_cb.desc = desc;
            dev->wr_cb.cb = (int (*)(int desc, void *args))args;
            return MDF_EOK;
        }

        default:
        {
#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
            if (dev->lock & MDF_LOCK_RDWR)
            {
                return MDF_EBUSY;
            }
            dev->lock |= MDF_LOCK_RDWR;
            dev->lock |= (desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */

            /* I/O control to the device */
            int ret = dev->ops->ioctl(dev, off, cmd, args);

#if (MDF_CFG_RDWR_LOCK == MDF_CFG_ENABLE)
            dev->lock &= ~MDF_LOCK_RDWR;
            dev->lock &= ~(desc & 0xffffff);
#endif /* MDF_CFG_RDWR_LOCK */
            return ret;
        }
    }
}

int mdf_dev_register(struct mdf_dev *dev,
                     const char *name,
                     uint32_t type,
                     uint32_t sflags,
                     struct mdf_dev_ops *ops,
                     struct mdf_drv *drv)
{
    MDF_ASSERT(dev != NULL);
    MDF_ASSERT(dev->magic != MDF_MAGIC);
    MDF_ASSERT(name != NULL);
    MDF_ASSERT(sflags != MDF_OFLAG_CLOSED);
    MDF_ASSERT(ops != NULL);
    MDF_ASSERT(drv != NULL || (sflags & MDF_SFLAG_NODRV));

    /* Initialize the fields */
    dev->magic = 0;
    memset(dev->name, '\0', MDF_NAME_MAX);
    mdf_list_init(&dev->list);
    mdf_list_init(&dev->slist);
    dev->link = NULL;
    dev->type = type;
    dev->sflags = sflags;
    dev->ref_count = 0;
    dev->lock = 0;

    dev->rd_cb.desc = -1;
    dev->rd_cb.cb = NULL;
    dev->wr_cb.desc = -1;
    dev->wr_cb.cb = NULL;

    dev->drv = drv;
    dev->ops = ops;

    return dev_register(dev, name);
}

static int desc_allocate(const char *name)
{
    int desc = -1;

    /* Find a free descriptor */
    for (int i = 0; i < MDF_DESC_MAX; i++)
    {
        if (desc_of(i).dev == NULL)
        {
            desc = i;
            break;
        }
    }
    if (desc < 0)
    {
        return MDF_ENOMEM;
    }

    /* Find the device */
    struct mdf_dev *dev = dev_find_or_register(name, NULL, 0);
    if (dev == NULL)
    {
        return MDF_ENOTFOUND;
    }

    /* Initialize the fields */
    desc_of(desc).dev = dev;
    desc_of(desc).offset = 0;
    desc_of(desc).oflags = MDF_OFLAG_CLOSED;
    return desc;
}

static void desc_free(int desc)
{
    if (desc >= 0 && desc < MDF_DESC_MAX)
    {
        desc_of(desc).dev = NULL;
        desc_of(desc).oflags = MDF_OFLAG_CLOSED;
        desc_of(desc).offset = 0;
    }
}

int mdf_dev_open(const char *name, uint32_t oflags)
{
    MDF_ASSERT(name != NULL);
    MDF_ASSERT(oflags != MDF_OFLAG_CLOSED);

    int desc = desc_allocate(name);
    if (desc < 0)
    {
        return desc;
    }

    int ret = dev_open(desc, oflags);
    if (ret != MDF_EOK)
    {
        desc_free(desc);
        return ret;
    }
    desc_of(desc).oflags = oflags;
    return desc;
}

int mdf_dev_close(int desc)
{
    MDF_ASSERT(desc_is_valid(desc));

    int ret = dev_close(desc);
    if (ret != MDF_EOK)
    {
        return ret;
    }

    desc_free(desc);
    return MDF_EOK;
}

ssize_t mdf_dev_read(int desc, void *buf, size_t size)
{
    MDF_ASSERT(desc_is_valid(desc));
    MDF_ASSERT(buf != NULL || size == 0);

#if (MDF_CFG_RDWR_CTRL == MDF_CFG_ENABLE)
    if (!(desc_of(desc).oflags & MDF_OFLAG_RDONLY))
    {
        return MDF_ENOTSUP;
    }
#endif /* MDF_CFG_RDWR_CTRL */

    /* Read buf from the device */
    return dev_read(desc, desc_of(desc).offset, buf, size, (desc_of(desc).oflags & MDF_OFLAG_NONBLOCK));
}

ssize_t mdf_dev_write(int desc, const void *buf, size_t size)
{
    MDF_ASSERT(desc_is_valid(desc));
    MDF_ASSERT(buf != NULL || size == 0);

#if (MDF_CFG_RDWR_CTRL == MDF_CFG_ENABLE)
    if (!(desc_of(desc).oflags & MDF_OFLAG_WRONLY))
    {
        return MDF_ENOTSUP;
    }
#endif /* MDF_CFG_RDWR_CTRL */

    /* Write buf to the device */
    return dev_write(desc, desc_of(desc).offset, buf, size, (desc_of(desc).oflags & MDF_OFLAG_NONBLOCK));
}

int mdf_dev_ioctl(int desc, int cmd, void *args)
{
    MDF_ASSERT(desc_is_valid(desc));

    switch (cmd)
    {
        case MDF_IOCTL_SET_OFFSET:
        {
            if (args != NULL)
            {
                desc_of(desc).offset = *(int *)args;
                return MDF_EOK;
            }
            return MDF_EINVAL;
        }

        case MDF_IOCTL_GET_OFFSET:
        {
            if (args != NULL)
            {
                *(int *)args = desc_of(desc).offset;
                return MDF_EOK;
            }
            return MDF_EINVAL;
        }

        default:
        {
            return dev_ioctl(desc, desc_of(desc).offset, cmd, args);
        }
    }
}

void mdf_dev_isr(struct mdf_dev *dev, uint32_t event)
{
    MDF_ASSERT(dev != NULL);

    if (dev->ops->isr != NULL)
    {
        size_t ret = dev->ops->isr(dev, event);
        switch (event)
        {
            case MDF_ISR_EVENT_RD_INTER:
            {
                if (ret != 0)
                {
                    if (dev->rd_cb.cb != NULL)
                    {
                        dev->rd_cb.cb(dev->rd_cb.desc, &ret);
                    }
                }
                return;
            }
            case MDF_ISR_EVENT_WR_INTER:
            {
                if (ret == 0)
                {
                    if (dev->wr_cb.cb != NULL)
                    {
                        dev->wr_cb.cb(dev->wr_cb.desc, &ret);
                    }
                }
                return;
            }

            default:
            {
                return;
            }
        }
    }
}
